package com.hedl.elasticsearch.service.impl;


import co.elastic.clients.elasticsearch.ElasticsearchClient;
import co.elastic.clients.elasticsearch._types.*;
import co.elastic.clients.elasticsearch._types.aggregations.*;
import co.elastic.clients.elasticsearch._types.query_dsl.*;
import co.elastic.clients.elasticsearch.core.SearchResponse;
import co.elastic.clients.elasticsearch.core.search.FieldSuggester;
import co.elastic.clients.elasticsearch.core.search.Hit;
import co.elastic.clients.elasticsearch.core.search.HitsMetadata;
import co.elastic.clients.elasticsearch.core.search.Suggestion;
import co.elastic.clients.json.JsonData;
import co.elastic.clients.util.ObjectBuilder;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.hedl.elasticsearch.mapper.HotelMapper;
import com.hedl.elasticsearch.pojo.Hotel;
import com.hedl.elasticsearch.pojo.HotelDoc;
import com.hedl.elasticsearch.pojo.PageResult;
import com.hedl.elasticsearch.pojo.RequestParams;
import com.hedl.elasticsearch.service.IHotelService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import java.io.IOException;
import java.util.*;

/**
 * @author Big Watermelon
 * @description
 * @date 2023/11/04/14:45
 */
@Slf4j
@Service
public class HotelServiceImpl extends ServiceImpl<HotelMapper,Hotel> implements IHotelService {

    private static final String INDEX_NAME = "hotel";

    @Resource
    private ElasticsearchClient client;


    /**
     * 根据字段搜索进行分页查询，同时把查询结果返回给前端
     * @param requestParams
     * @return
     * @throws IOException
     */
    @Override
    public PageResult search(RequestParams requestParams) throws IOException {
        SearchResponse<HotelDoc> search;
        if (requestParams.getKey() ==  null|| "".equals(requestParams.getKey())){
            search = client.search(searchRequest -> searchRequest.index(INDEX_NAME)
                            .query(query -> query.matchAll(MatchAllQuery.of(pi->pi)))
                            .from((requestParams.getPage()-1)*requestParams.getSize())
                            .size(requestParams.getSize())
                    , HotelDoc.class);
        }else {
            search = client.search(searchRequest -> searchRequest.index(INDEX_NAME)
                            .query(query -> query.match(
                                    matchQuery -> matchQuery.field("all").query(requestParams.getKey())
                            ))
                            .from((requestParams.getPage()-1)*requestParams.getSize())
                            .size(requestParams.getSize())
                    , HotelDoc.class);
        }
        return result(search);
    }

    /**
     * 查询全部数据，并指定过滤条件
     * @param requestParams
     * @return
     * @throws IOException
     */
    @Override
    public PageResult boolQuery(RequestParams requestParams) throws IOException {
        SearchResponse<HotelDoc> search = client.search(
                searchRequest -> searchRequest.index(INDEX_NAME)
                        .query(
                                query->query.bool(
                                        b->b.must(
                                                q->q.matchAll(
                                                        MatchAllQuery.of(pi->pi)
                                                ))
                                                .filter(q->q.term(
                                                        tq->tq.field("city").value(requestParams.getCity())
                                                ))
                                                .filter(q->q.term(
                                                        tq->tq.field("brand").value(requestParams.getBrand())
                                                ))
                                                .filter(q->q.term(
                                                        tq->tq.field("starName").value(requestParams.getStarName())
                                                ))
                                                .filter(q->q.range(
                                                        rq->rq.field("price")
                                                                .gte(JsonData.of(requestParams.getMinPrice()))
                                                                .lte(JsonData.of(requestParams.getMaxPrice()))
                                                ))
                                )
                        )
                , HotelDoc.class);
        return result(search);
    }

    /**
     * 查询全部数据，并根据当前位置进行范围查询，然后进行排序，同时进行分页
     * @param requestParams
     * @return
     * @throws IOException
     */
    @Override
    public PageResult distanceSort(RequestParams requestParams) throws IOException {
        String location = requestParams.getLocation();
        String[] split = location.split(",");
        if (split.equals(null)){
            System.out.println("错误请求！");
        }
        SearchResponse<HotelDoc> search = client.search(
                searchRequest -> searchRequest.index(INDEX_NAME)
                        .query(
                                query -> query.matchAll(MatchAllQuery.of(pi -> pi))
                        )
                        .sort(
                                sortOptions -> sortOptions.geoDistance(
                                        geoDistanceSort -> geoDistanceSort.field("location")
                                                .location(geoLocation -> geoLocation.latlon(
                                                        latLonGeoLocation -> latLonGeoLocation
                                                                .lat(Double.valueOf(split[0]))
                                                                .lon(Double.valueOf(split[1]))
                                                ))
                                                .order(SortOrder.Asc)
                                                .unit(DistanceUnit.Kilometers)
                                )
                        )
                        .from((requestParams.getPage()-1)*requestParams.getSize())
                        .size(requestParams.getSize())
                , HotelDoc.class);
        return result(search);
    }


    /**
     * 提前我们在es的hotel数据中，添加了isAD字段，值为true，表示有isAD字段的数据要进行广告置顶
     * 在HotelDoc类中也要添加isAD属性
     * 共6条广告
     * @param requestParams
     * @return
     * @throws IOException
     */
    @Override
    public PageResult functionScore(RequestParams requestParams) throws IOException {
        SearchResponse<HotelDoc> search = client.search(
                searchRequest -> searchRequest.index(INDEX_NAME)
                        .query(query -> query.functionScore(
                                FunctionScoreQuery.of(
                                        fsq -> fsq.query(
                                                        q -> {
                                                            if (requestParams.getKey() == null || "".equals(requestParams.getKey())) {
                                                                return q.matchAll(MatchAllQuery.of(pi -> pi));
                                                            } else {
                                                                return q.match(matchQuery -> matchQuery.field("city")
                                                                        .query(requestParams.getKey()));
                                                            }
                                                        }
                                                )
                                                .functions(
                                                        functionScore -> functionScore.filter(
                                                                        q -> q.term(
                                                                                termQuery -> termQuery.field("isAD")
                                                                                        .value(true)
                                                                        )
                                                                )
                                                                .weight(10.00)
                                                ).boostMode(FunctionBoostMode.Sum)
                                )
                        ))
                        .from((requestParams.getPage() - 1) * requestParams.getSize())
                        .size(requestParams.getSize())
                , HotelDoc.class);
        return result(search);
    }

    /**
     * 聚合查询然后解析聚合查询的json结果，封装成 Map<String, List<String>> 返回
     * @return
     * @throws IOException
     */
    @Override
    public Map<String, List<String>> filters(RequestParams requestParams) throws IOException {

        List<Query> filterList = getFilterList(requestParams);

        SearchResponse<HotelDoc> search = client. search(searchRequest -> searchRequest.index("hotel")
                        .query(query->query.bool(
                                bootQuery->{
                                    if (requestParams.getKey() == null || "".equals(requestParams.getKey())){
                                        bootQuery.must(
                                                q->q.matchAll(MatchAllQuery.of(pi->pi))
                                        );
                                    }else {
                                        bootQuery.must(
                                                q -> q.match(matchQuery -> matchQuery.field("all").query(requestParams.getKey())
                                                ));
                                    }
                                    bootQuery.filter(filterList);
                                return bootQuery;
                                }
                        ))
                        .size(0)
                        .aggregations("brandAgg",aggregation->aggregation.terms(
                                termsAggregation->termsAggregation.field("brand").size(5)
                        ))
                        .aggregations("cityAgg",aggregation->aggregation.terms(
                                termsAggregation->termsAggregation.field("city").size(5)
                        ))
                        .aggregations("starNameAgg",aggregation->aggregation.terms(
                                termsAggregation->termsAggregation.field("starName").size(5)
                        ))
                , HotelDoc.class);

        return bucketsResult(search,"cityAgg","starNameAgg","brandAgg");
    }

    @Override
    public List<String> suggestion(String key) throws IOException {
        Map<String, FieldSuggester> map = new HashMap<>();
        FieldSuggester fieldSuggester = FieldSuggester.of(
                pi->pi.text(key).completion(
                        completionSuggester->completionSuggester.field("suggestion").skipDuplicates(true).size(10)
                )
        );
        map.put("text",fieldSuggester);     //这里的key要对应到，自定义的一个key


        SearchResponse<HotelDoc> search = client.search(
                searchRequest -> searchRequest.index("hotel")
                        .suggest(suggester->suggester.suggesters(map))
                , HotelDoc.class);

        return suggestionResult(search);
    }

    private List<String> suggestionResult(SearchResponse<HotelDoc> search){
        List<String> list = new ArrayList<>();
        List<Suggestion<HotelDoc>> suggestions = search.suggest().get("text");      //这里来使用
        suggestions.forEach(
                suggestion->{
                    suggestion.completion().options().forEach(
                            s->{
                                //System.out.println(s.source());
                                System.out.println(s.text());
                                list.add(s.text());
                            }
                    );
                }
        );
        return list;
    }

    private static List<Query> getFilterList(RequestParams requestParams) {
        List<Query> filterList = new ArrayList<>();

        if (requestParams.getCity() != null && !requestParams.getCity().equals("")){
            Query filterQuery = Query.of(pi->{
                pi.term(termQuery->termQuery.field("city").value(requestParams.getCity()));
                return pi;
            });
            filterList.add(filterQuery);
        }
        if (requestParams.getBrand() != null && !requestParams.getBrand().equals("")){
            Query filterQuery = Query.of(pi->{
                pi.term(termQuery->termQuery.field("brand").value(requestParams.getBrand()));
                return pi;
            });
            filterList.add(filterQuery);
        }

        if (requestParams.getStarName() != null && !requestParams.getStarName().equals("")){
            Query filterQuery = Query.of(pi->{
                pi.term(termQuery->termQuery.field("starName").value(requestParams.getStarName()));
                return pi;
            });
            filterList.add(filterQuery);
        }

        if (requestParams.getMinPrice() != 0 && requestParams.getMaxPrice() != 0){
            Query filterQuery = Query.of(pi->{
                pi.range(rangeQuery->rangeQuery.field("price").gte(JsonData.of(requestParams.getMinPrice()))
                        .lte(JsonData.of(requestParams.getMaxPrice())));
                return pi;
            });
            filterList.add(filterQuery);
        }
        return filterList;
    }

    @Override
    public Map<String, List<String>> bucketsResult(SearchResponse<HotelDoc> search,String ... fieldName){

        log.info(Arrays.toString(fieldName));

        Map<String, List<String>> map = new HashMap<>();

        List<String> cityList = new ArrayList<>();
        List<StringTermsBucket> cityAgg = search.aggregations().get(fieldName[0]).sterms().buckets().array();
        cityAgg.forEach(
                city->{
                    String s = city.key().stringValue();
                    cityList.add(s);
                    map.put("城市",cityList);
                }
        );

        List<String> starNameList = new ArrayList<>();
        List<StringTermsBucket> starNameAgg = search.aggregations().get(fieldName[1]).sterms().buckets().array();
        starNameAgg.forEach(
                starName->{
                    String s = starName.key().stringValue();
                    starNameList.add(s);
                    map.put("星级",starNameList);
                }
        );

        List<String> brandList = new ArrayList<>();
        List<StringTermsBucket> brandAgg = search.aggregations().get(fieldName[2]).sterms().buckets().array();
        brandAgg.forEach(
                brand->{
                    String s = brand.key().stringValue();
                    brandList.add(s);
                    map.put("品牌",brandList);
                }
        );
        System.out.println(map);
        return map;
    }

    /**
     * es返回数据的解析
     * @param search
     * @return
     */
    private PageResult result(SearchResponse<HotelDoc> search){
        HitsMetadata<HotelDoc> hits = search.hits();

        List<HotelDoc> list = new ArrayList<>();

        List<Hit<HotelDoc>> hotelDocs = hits.hits();
        hotelDocs.forEach(
                hotelDoc->{
                    List<FieldValue> sorts = hotelDoc.sort();
                    if (sorts.size() != 0) {
                        sorts.forEach(
                                sort->{
                                    hotelDoc.source().setDistance(Double.valueOf(String.format("%.2f", sort.doubleValue())));
                                }
                        );
                        list.add(hotelDoc.source());
                    }else {
                        list.add(hotelDoc.source());
                    }
                }
        );
        PageResult pageResult = new PageResult(hits.total().value(),list);
        return pageResult;
    }
}
